

import { useState,useEffect,useImperativeHandle,MutableRefObject, DependencyList, Ref } from "react";
export interface CarouselOptions {
    el: string | HTMLElement;
    speed: number; // 轮播速度(ms)
    delay: number; // 轮播延迟(ms)
    direction: 'left' | 'right'; // 图片滑动方向
    monitorKeyEvent: boolean; // 是否监听键盘事件
    monitorTouchEvent: boolean; // 是否监听屏幕滑动事件
}
class Carousel {
    container: HTMLElement;
    carouselItems?: NodeList;
    carouselSigns?: NodeList;
    carouselCtrlL?: HTMLElement;
    carouselCtrlR?: HTMLElement;
    curIndex: number = 0;
    numItems: number = 0;
    status: boolean = true;
    speed: number = 600;
    delay: number = 3000;
    direction: CarouselOptions['direction'] = 'left';
    monitorKeyEvent: boolean = false;
    monitorTouchEvent: boolean = false;
    interval?: ReturnType<typeof setInterval>;
    startX: number = 0;
    startY: number = 0;
    endX: number = 0;
    endY: number = 0;
    constructor(options: CarouselOptions){
        this.container = typeof options.el === 'string' ? this.$(options.el) as HTMLElement : options.el as HTMLElement;
        if(!this.container){
            console.warn(`[Carousel warning]: Can not find the container element!`);
            return;
        }
        this.carouselItems = this.$$('.carousel-item',this.container) as NodeList;
        this.carouselSigns = this.$$(('.carousel-sign .carousel-sign-item'),this.container) as NodeList;
        this.carouselCtrlL = this.$$(('.carousel-ctrl'),this.container)[0] as HTMLElement;
        this.carouselCtrlR = this.$$(('.carousel-ctrl'),this.container)[1] as HTMLElement;

        this.curIndex = 0;

        this.numItems = this.carouselItems.length;

        // 是否可以滑动
        this.status = true;

        // 轮播速度
        this.speed = options.speed || 600;
        // 等待延时
        this.delay = options.delay || 3000;

        // 轮播方向
        this.direction = options.direction || 'left';

        // 是否监听键盘事件
        this.monitorKeyEvent = options.monitorKeyEvent || false;
        // 是否监听屏幕滑动事件
        this.monitorTouchEvent = options.monitorTouchEvent || false;
        this.handleEvents();
        this.setTransition();
    } 
    start() {
        const event = {
            srcElement: this.direction == 'left' ? this.carouselCtrlR : this.carouselCtrlL
        };
        const clickCtrl = this.clickCtrl.bind(this);
        // 每隔一段时间模拟点击控件
        this.interval = setInterval(clickCtrl, this.delay, event);
    }
    clickCtrl(event: Event) {
        if (!this.status) return;
        this.status = false;
        let fromIndex = this.curIndex, toIndex, direction;
        if (event.srcElement == this.carouselCtrlR) {
            toIndex = (this.curIndex + 1) % this.numItems;
            direction = 'left';
        } else {
            toIndex = (this.curIndex + this.numItems - 1) % this.numItems;
            direction = 'right';
        }
        this.slide(fromIndex, toIndex, direction as CarouselOptions['direction']);
        this.curIndex = toIndex;
    }
    pause() {
        clearInterval(this.interval);
    }
    clickSign(event: Event) {
        if (!this.status) return;
        this.status = false;
        const fromIndex = this.curIndex;
        const toIndex = parseInt((event.srcElement as HTMLElement)?.getAttribute('slide-to') as string);
        const direction = fromIndex < toIndex ? 'left' : 'right';
        this.slide(fromIndex, toIndex, direction);
        this.curIndex = toIndex;
    }
    handleEvents(){
        // 鼠标移动到轮播盒上时继续轮播
        this.container.addEventListener('mouseleave', this.start.bind(this));
        // 鼠标从轮播盒上移开时暂停轮播
        this.container.addEventListener('mouseover', this.pause.bind(this));

        // 点击左侧控件向右滑动图片
        this.carouselCtrlL?.addEventListener('click', this.clickCtrl.bind(this));
        // 点击右侧控件向左滑动图片
        this.carouselCtrlR?.addEventListener('click', this.clickCtrl.bind(this));

        const signs = this.carouselSigns as NodeList;
        // 点击轮播标志后滑动到对应的图片
        for (let i = 0; i < signs.length; i++) {
            (signs[i] as HTMLElement).setAttribute('slide-to', i + '');
            signs[i].addEventListener('click', this.clickSign.bind(this));
        }

        // 监听键盘事件
        if (this.monitorKeyEvent) {
            document.addEventListener('keydown', this.keyDown.bind(this));
        }

        // 监听屏幕滑动事件
        if (this.monitorTouchEvent) {
            this.container.addEventListener('touchstart', this.touchScreen.bind(this));
            this.container.addEventListener('touchend', this.touchScreen.bind(this));
        }
    }
    touchScreen(event: TouchEvent) {
        if (event.type == 'touchstart') {
            this.startX = event.touches[0].pageX;
            this.startY = event.touches[0].pageY;
        } else {  // touchend
            this.endX = event.changedTouches[0].pageX;
            this.endY = event.changedTouches[0].pageY;

            // 计算滑动方向的角度
            const dx = this.endX - this.startX
            const dy = this.startY - this.endY;
            const angle = Math.abs(Math.atan2(dy, dx) * 180 / Math.PI);

            // 滑动距离太短
            if (Math.abs(dx) < 10 || Math.abs(dy) < 10) return;

            if (angle >= 0 && angle <= 45) {
                // 向右侧滑动屏幕，模拟点击左控件
                this.carouselCtrlL?.click();
            } else if (angle >= 135 && angle <= 180) {
                // 向左侧滑动屏幕，模拟点击右控件
                this.carouselCtrlR?.click();
            }
        }
    }
    keyDown(event: KeyboardEvent) {
        if (event && event.keyCode == 37) {
            this.carouselCtrlL?.click();
        } else if (event && event.keyCode == 39) {
            this.carouselCtrlR?.click();
        }
    }
    setTransition(){
        const styleElement = document.createElement('style');
        document.head.appendChild(styleElement);
        const styleRule = `.carousel-item {transition: left ${this.speed}ms ease-in-out}`
        styleElement?.sheet?.insertRule(styleRule, 0);
    }
    slide(fromIndex: number, toIndex: number, direction: CarouselOptions['direction']) {
        let fromClass: string, toClass: string;
        const items = this.carouselItems as NodeList;
        const fromItem = items[fromIndex] as HTMLElement;
        const toItem = items[toIndex] as HTMLElement;
        const signs = this.carouselSigns as NodeList;
        const fromSign = signs[fromIndex] as HTMLElement;
        const toSign = signs[toIndex] as HTMLElement;
        if (direction == 'left') {
            toItem.className = "carousel-item next";
            fromClass = 'carousel-item active left';
            toClass = 'carousel-item next left';
        } else {
            toItem.className = "carousel-item prev";
            fromClass = 'carousel-item active right';
            toClass = 'carousel-item prev right';
        }
        fromSign.className = "carousel-sign-item";
        toSign.className = "carousel-sign-item active";

        setTimeout((() => {
            fromItem.className = fromClass;
            toItem.className = toClass;
        }).bind(this), 50);

        setTimeout((() => {
            fromItem.className = 'carousel-item';
            toItem.className = 'carousel-item active';
            this.status = true;  // 设置为可以滑动
        }).bind(this), this.speed + 50);
    }
    $(v: string,el: HTMLElement | Document = document) {
        return el.querySelector(v);
    };
    $$(v: string,el: HTMLElement | Document = document){
        return el.querySelectorAll(v);
    }
}

const useCarousel = (options?: CarouselOptions) => {
    const [instance,setInstance] = useState<Carousel | null>(null);
    const carouselOptions = options || {
        el: '.carousel-box',
        speed: 1000, // 轮播速度(ms)
        delay: 0, // 轮播延迟(ms)
        direction: 'left', // 图片滑动方向
        monitorKeyEvent: true, // 是否监听键盘事件
        monitorTouchEvent: true // 是否监听屏幕滑动事件
    }
    useEffect(() => {
        const carouselInstance = new Carousel(carouselOptions);
        carouselInstance.start();
        setInstance(carouselInstance);
    },[]);
    return instance;
}
export const useChildrenHandler = <T, K extends object>(
  originRef: MutableRefObject<T>,
  handler: K,
  deps?: DependencyList,
): void =>
  useImperativeHandle(
    originRef,
    () => {
      return {
        ...originRef.current,
        ...handler,
      };
    },
    deps,
  );

export type ImperFunc = (...args: any[]) => any;
export type ImperRef = Record<string, ImperFunc>;
export type ImperItem = {
  ref: Ref<ImperRef>;
};
export { Carousel as CarouselInstance };
export default useCarousel